﻿using System;
using System.Net;
using System.Net.Sockets;
using System.Collections;
using System.Runtime.InteropServices;

namespace System.Net.Sockets
{
    internal unsafe class Native
    {
        [DllImport("msvcrt.dll")]
        public static extern void* memcpy(byte[] dst, byte[] src, int size);
    }

    public unsafe interface IPacketCipher
    {
        void Encrypt(byte[] In, byte[] Out, int Size);
        void Encrypt(byte* In, byte[] Out, int Size);
        void Decrypt(byte[] In, byte[] Out, int Size);
    }

    public unsafe class HybridWinsockClient
    {
        private static uint socketUID = 0;
        private ServerSocket server;
        private Socket connection;
        private uint UID;

        public SocketEvent<HybridWinsockClient, object> SocketCorrupt;
        public object Wrapper;
        public IPacketCipher Crypto;
        public byte[] Buffer;
        public int RecvSize;
        public bool Connected;

        public ServerSocket Server { get { return server; } }
        public Socket Connection { get { return connection; } }
        public uint UniqueID { get { return UID; } }

        public HybridWinsockClient(ServerSocket _Server, Socket _Connection, int BufferSize)
        {
            server = _Server;
            connection = _Connection;
            UID = socketUID++;
            Buffer = new byte[BufferSize];
            RecvSize = 0;
            Wrapper = null;
        }
        public bool Send(byte[] Packet)
        {
            try
            {
                if (Crypto != null)
                {
                    byte[] dest = new byte[Packet.Length];
                    Crypto.Encrypt(Packet, dest, dest.Length);
                    connection.Send(dest);
                }
                else
                    connection.Send(Packet);
                return true;
            }
            catch
            {
                return false;
            }
        }
        public void Disconnect()
        {
            try
            {
                Connection.Disconnect(false);
            }
            catch
            {
                Server.InvokeDisconnect(this);
            }
        }
    }

    public delegate void SocketEvent<T, T2>(T Sender, T2 Arg);

    public class ConquerSocket : ServerSocket
    {
        protected override IPacketCipher MakeCrypto()
        {
            return new Encryptions.ConquerStanderedCipher();
        }
    }

    public abstract class ServerSocket
    {
        private Socket Connection;
        private ushort port;
        private int backlog;
        private bool enabled;
        private void enabledCheck(string Variable)
        {
            if (enabled)
                throw new Exception("Cannot modify " + Variable + " while socket is enabled.");
        }
        private int clientbuffersize;

        public ushort Port { get { return port; } set { enabledCheck("Port"); port = value; } }
        public int Backlog { get { return backlog; } set { enabledCheck("Backlog"); backlog = value; } }
        public int ClientBufferSize { get { return clientbuffersize; } set { enabledCheck("ClientBufferSize"); clientbuffersize = value; } }
        public bool Enabled { get { return enabled; } }

        public SocketEvent<HybridWinsockClient, object> OnClientConnect;
        public SocketEvent<HybridWinsockClient, object> OnClientDisconnect;
        public SocketEvent<HybridWinsockClient, byte[]> OnClientReceive;
        public SocketEvent<HybridWinsockClient, SocketError> OnClientError;

        protected abstract IPacketCipher MakeCrypto();

        public ServerSocket()
        {
            Connection = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            clientbuffersize = 0xffff;
        }
        public void Enable()
        {
            if (!enabled)
            {
                Connection.Bind(new IPEndPoint(IPAddress.Any, (int)port));
                Connection.Listen(backlog);
                Connection.BeginAccept(new AsyncCallback(AsyncConnect), null);
                enabled = true;
            }
        }
        public void Disable()
        {
            if (enabled)
            {
                Connection.Close();
                enabled = false;
            }
        }

        private void AsyncConnect(IAsyncResult res)
        {
            byte ConnectionPhase = 0;
            HybridWinsockClient Client = null;
            try
            {
                Client = new HybridWinsockClient(this, Connection.EndAccept(res), clientbuffersize);
                Client.Crypto = MakeCrypto();
                Client.Connected = true;
                ConnectionPhase++;
                if (OnClientConnect != null)
                    OnClientConnect.Invoke(Client, null);
                Connection.BeginAccept(new AsyncCallback(AsyncConnect), null);
                Client.Connection.BeginReceive(Client.Buffer, 0, Client.Buffer.Length, SocketFlags.None, new AsyncCallback(AsyncReceive), Client);
                ConnectionPhase++;
            }
            catch (SocketException)
            {
                if (enabled)
                {
                    Connection.BeginAccept(new AsyncCallback(AsyncConnect), null);
                }
            }
            catch (ObjectDisposedException) { }
        }
        private unsafe void AsyncReceive(IAsyncResult res)
        {
            try
            {
                HybridWinsockClient Client = (HybridWinsockClient)res.AsyncState;
                SocketError Error;
                Client.RecvSize = Client.Connection.EndReceive(res, out Error);
                if (Error == SocketError.Success && Client.RecvSize > 0 && Client.Connection.Connected)
                {
                    byte[] dest = new byte[Client.RecvSize];
                    if (Client.Crypto != null)
                        Client.Crypto.Decrypt(Client.Buffer, dest, dest.Length);
                    else
                        Native.memcpy(dest, Client.Buffer, Client.RecvSize);

                    if (OnClientReceive != null)
                        OnClientReceive.Invoke(Client, dest);
                    Client.Connection.BeginReceive(Client.Buffer, 0, Client.Buffer.Length, SocketFlags.None, new AsyncCallback(AsyncReceive), Client);
                }
                else
                {
                    Client.Connected = false;
                    if (Error != SocketError.Success)
                    {
                        if (OnClientError != null)
                            OnClientError.Invoke(Client, Error);
                    }
                    InvokeDisconnect(Client);
                }
            }
            catch (SocketException) { }
            catch (ObjectDisposedException) { }
        }
        public void InvokeDisconnect(HybridWinsockClient Client)
        {
            if (Client != null)
            {
                try
                {
                    if (Client.Connected)
                    {
                        Client.Connection.Shutdown(SocketShutdown.Both);
                        Client.Connection.Close();
                    }
                    else
                    {
                        if (OnClientDisconnect != null)
                            OnClientDisconnect.Invoke(Client, null);
                        Client.Wrapper = null;
                        Client = null;
                    }
                }
                catch (ObjectDisposedException) { }
            }
        }
    }
}